/* ========== QuestBoard v0.7.6 + 类别持久化修复 ========== */

const LS_KEY = 'questboard_v4';
// 默认类别仅作为后备，不再强制重置
const DEFAULT_CATS = ['EcN-T6SS平台','科室科研','科室管理','公司兼职','党群工作','小家庭','大家庭','其他'];

const uiState = { filterCategory: '', filterStatus: '', filterSearch: '' };

const state = {
  tasks: [],
  stats: { mvdByDate: {}, pomodoroByDate: {}, minutesByDate: {} },
  categories: [], // 初始为空，等待加载
  packages: [],
  timer: null,
  remaining: 0,
  timerTaskId: null,
  timerOrigSeconds: null,
  meta: { updatedAt: 0, notifyEnabled: true, notified: {} }
};

// ===== Utils =====
function escapeHtml(s){ return (s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[m])); }
const el  = (s)=>document.querySelector(s);
const els = (s)=>Array.from(document.querySelectorAll(s));
const now = ()=>new Date();
const dateKey = (d)=> d.toISOString().slice(0,10);
const uid = ()=> Math.random().toString(36).slice(2,9);
const toInt = (x)=> Number.isFinite(Number(x)) ? Number(x) : 0;

const saveLocal = ()=>{ state.meta.updatedAt = Date.now(); localStorage.setItem(LS_KEY, JSON.stringify(state)); maybeAutoSaveFile(); };
const loadLocal = ()=>{ const raw = localStorage.getItem(LS_KEY); if(!raw) return; try{ Object.assign(state, JSON.parse(raw)); }catch(e){} };

// ** FIXED in v0.7.6: 更温和的 fixupState，保护你的自定义类别 **
function fixupState(){
  // 只有当 state.categories 真的完全不存在或不是数组时，才初始化它
  if (!Array.isArray(state.categories)) {
      state.categories = [];
  }
  // 如果初始化后还是空的，才用默认列表填充。这避免了覆盖已有数据。
  if (state.categories.length === 0) {
      state.categories = DEFAULT_CATS.slice();
  }

  // 确保几个核心类别始终存在
  ['其他','科室科研'].forEach(c=>{ if(!state.categories.includes(c)) state.categories.push(c); });

  state.meta = Object.assign({updatedAt:0, notifyEnabled:true, notified:{}}, state.meta||{});
  state.packages = Array.isArray(state.packages)? state.packages: [];
  state.stats = Object.assign({mvdByDate:{}, pomodoroByDate:{}, minutesByDate:{}}, state.stats||{});
  (state.tasks||[]).forEach(t=>{
    if(!t.status) t.status='today';
    if(!t.timeLogs) t.timeLogs=[];
    if(!t.kind) t.kind='core';
    if(typeof t.weight==='undefined') t.weight=5;
  });
}

// ===== Categories (no changes) =====
function refreshCategoriesUI(){
  refreshPoolFilter();
  const catSel = el('#category'); const filterSel=el('#filterCategory'); const quick=el('#quickCats'); const catList=el('#catList'); const qcSel=el('#qcCategory');
  if(catSel){ catSel.innerHTML=''; state.categories.forEach(c=>{ const o=document.createElement('option'); o.textContent=c; o.value=c; catSel.appendChild(o); }); }
  if(filterSel){
    filterSel.innerHTML=''; const o=document.createElement('option'); o.value=''; o.textContent='全部'; filterSel.appendChild(o);
    state.categories.forEach(c=>{ const op=document.createElement('option'); op.value=c; op.textContent=c; filterSel.appendChild(op); });
  }
  if(qcSel){ qcSel.innerHTML=''; state.categories.forEach(c=>{ const op=document.createElement('option'); op.value=c; op.textContent=c; qcSel.appendChild(op); }); }
  if(quick){
    quick.innerHTML='';
    const all = document.createElement('button'); all.className='badge bg-slate-100 hover:bg-slate-200'; all.textContent='全部'; all.onclick=()=>{ el('#filterCategory').value=''; updateAndRender(); }; quick.appendChild(all);
    state.categories.forEach(c=>{ const b=document.createElement('button'); b.className='badge bg-slate-100 hover:bg-slate-200'; b.textContent=c; b.onclick=()=>{ el('#filterCategory').value=c; updateAndRender(); }; quick.appendChild(b); });
  }
  if(catList){
    catList.innerHTML='';
    state.categories.forEach(c=>{
      const row=document.createElement('div'); row.className='cat-row';
      const nm=document.createElement('span'); nm.className='name'; nm.textContent=c; row.appendChild(nm);
      const be=document.createElement('button'); be.textContent='✎ 重命名'; be.onclick=()=>renameCategory(c); row.appendChild(be);
      const bd=document.createElement('button'); bd.textContent='× 删除'; bd.onclick=()=>deleteCategory(c); row.appendChild(bd);
      catList.appendChild(row);
    });
  }
}
function addCategory(){
  const input=el('#newCategory'); const name=(input.value||'').trim(); if(!name) return alert('请输入类别名称');
  if(state.categories.includes(name)) return alert('已存在该类别');
  state.categories.push(name); saveLocal(); refreshCategoriesUI(); input.value='';
}
function renameCategory(oldName){
  if(oldName==='其他') return alert('“其他”不能重命名');
  const nn=prompt('新名称：', oldName); if(!nn) return; const name=nn.trim(); if(!name) return alert('名称不能为空');
  if(state.categories.includes(name)) return alert('已存在该类别');
  state.tasks.forEach(t=>{ if(t.category===oldName) t.category=name; });
  const i=state.categories.indexOf(oldName); if(i>=0) state.categories[i]=name; saveLocal(); refreshCategoriesUI(); updateAndRender();
}
function deleteCategory(name){
  if(name==='其他') return alert('“其他”不能删除');
  if(!confirm(`删除类别“${name}”？该类别下任务将移到“其他”。`)) return;
  state.tasks.forEach(t=>{ if(t.category===name) t.category='其他'; });
  state.categories = state.categories.filter(c=>c!==name); saveLocal(); refreshCategoriesUI(); updateAndRender();
}

// ===== Stats (no changes) =====
function addMVD(){ const k=dateKey(now()); state.stats.mvdByDate[k]=(state.stats.mvdByDate[k]||0)+1; saveLocal(); renderStats(); }
function addPomodoro(){ const k=dateKey(now()); state.stats.pomodoroByDate[k]=(state.stats.pomodoroByDate[k]||0)+1; saveLocal(); renderStats(); }
function addMinutesToStats(dk, m){ state.stats.minutesByDate[dk]=(state.stats.minutesByDate[dk]||0)+m; }
function renderStats(){
  const k=dateKey(now());
  el('#statMVD').textContent=state.stats.mvdByDate[k]||0;
  el('#statPomodoro').textContent=state.stats.pomodoroByDate[k]||0;
}

// ===== Time Logging (no changes) =====
function totalMinutes(t){ return (t.timeLogs||[]).reduce((a,b)=> a + toInt(b.minutes), 0); }
function logMinutes(t, minutes, dk){
  const m = Math.max(0, toInt(minutes)); if(!m) return;
  if(!t.timeLogs) t.timeLogs=[];
  const key = dk || dateKey(now());
  t.timeLogs.push({ date:key, minutes:m });
  addMinutesToStats(key, m);
  saveLocal(); updateAndRender();
}
function promptAddMinutes(t){
  const minStr = prompt('填写本次用时（分钟）：', '25'); if(minStr===null) return;
  const m = Math.max(0, toInt(minStr)); if(!m) return;
  const dk = prompt('日期（回车=今天，格式YYYY-MM-DD）：', '')||'';
  const ok = (!dk || /^\d{4}-\d{2}-\d{2}$/.test(dk));
  if(!ok) return alert('日期格式应为 YYYY-MM-DD');
  logMinutes(t, m, dk||undefined);
}

// ===== Card Rendering (no changes) =====
function statusLabel(s){ return {today:'今日',progress:'进行中',backlog:'待办',done:'完成'}[s]||s; }
function nextStatus(s){ const order=['today','progress','backlog','done']; const i=order.indexOf(s); return order[(i+1)%order.length]; }
function repeatLabel(r){ return {daily:'每日', weekly:'每周', monthly:'每月'}[r] || ''; }
function kindLabel(k){ return {core:'主业', enabling:'赋能', admin:'行政', life:'生活'}[k] || k || '主业'; }
function kindClass(k){ return {core:'core',enabling:'enabling',admin:'admin',life:'life'}[k]||'core'; }
function buildCard(t){
  const tpl = el('#taskCardTpl').content.cloneNode(true);
  const wrap = tpl.querySelector('.task'); wrap.dataset.id=t.id;
  tpl.querySelector('.title').textContent=t.title;
  tpl.querySelector('.category').textContent=t.category;
  tpl.querySelector('.status').textContent=statusLabel(t.status);
  const kf = tpl.querySelector('.kindflag');
  if(kf){ kf.textContent = kindLabel(t.kind); kf.classList.add('kindflag', kindClass(t.kind)); kf.classList.remove('hidden'); }
  const dueEl = tpl.querySelector('.due'); const flag=tpl.querySelector('.dueflag'); const rflag=tpl.querySelector('.repeatflag');
  wrap.classList.remove('overdue','soon');
  if(t.due){ const d=new Date(t.due); const ms=d-now(); const mins=Math.round(ms/60000); dueEl.textContent='截止：'+d.toLocaleString();
    if(t.status!=='done'){
      if(ms<0){ wrap.classList.add('overdue'); flag.className='badge badge-rose dueflag'; flag.textContent='已逾期'; flag.classList.remove('hidden'); }
      else if(mins<=180){ wrap.classList.add('soon'); flag.className='badge badge-amber dueflag'; flag.textContent=`${mins} 分钟内到期`; flag.classList.remove('hidden'); }
    }
  } else { dueEl.textContent='未设截止'; }
  if(t.repeat && t.repeat!=='none'){ rflag.className='badge badge-green repeatflag'; rflag.textContent=repeatLabel(t.repeat); rflag.classList.remove('hidden'); }
  const ul=tpl.querySelector('.dod');
  (t.dod||[]).forEach((d,i)=>{
    const li=document.createElement('li');
    li.innerHTML='<label class="inline-flex items-start gap-2"><input data-idx="'+i+'" type="checkbox" '+(d.done?'checked':'')+' class="dodCheck mt-1"> <span>'+escapeHtml(d.text)+'</span></label>';
    ul.appendChild(li);
  });
  const notesEl = tpl.querySelector('.notes'); notesEl.value=t.notes||'';
  const timeSumEl = tpl.querySelector('.timeSum'); if(timeSumEl){ timeSumEl.textContent = totalMinutes(t); }
  tpl.querySelector('.btnStart').onclick=()=>startPomodoro(25*60, t.id);
  const bAdd = tpl.querySelector('.btnAddMinutes'); if(bAdd) bAdd.onclick=()=>promptAddMinutes(t);
  tpl.querySelector('.btnMVD').onclick=()=>completeTask(t);
  tpl.querySelector('.btnMove').onclick=()=>{ t.status=nextStatus(t.status); saveLocal(); updateAndRender(); };
  tpl.querySelector('.btnDel').onclick=()=>{ if(confirm('删除该任务？')){ state.tasks=state.tasks.filter(x=>x.id!==t.id); saveLocal(); updateAndRender(); }};
  notesEl.onchange=(e)=>{ t.notes=e.target.value; saveLocal(); };
  ul.addEventListener('change',(e)=>{ if(e.target.classList.contains('dodCheck')){ const idx=Number(e.target.dataset.idx); t.dod[idx].done=e.target.checked; saveLocal(); }});
  tpl.querySelector('.btnSnooze1h').onclick=()=>snooze(t, 60);
  tpl.querySelector('.btnSnooze1d').onclick=()=>snooze(t, 24*60);
  tpl.querySelector('.btnAttachPEM').onclick=()=>openAttachPEM(t.id);
  wrap.addEventListener('dragstart', (e)=>{ e.dataTransfer.setData('text/plain', t.id); wrap.classList.add('dragging'); });
  wrap.addEventListener('dragend', ()=> wrap.classList.remove('dragging'));
  return tpl;
}

// ===== Main Render (from v0.7.4) =====
function updateAndRender() {
    captureUIState();
    render();
}
function captureUIState() {
    uiState.filterCategory = el('#filterCategory').value;
    uiState.filterStatus = el('#filterStatus').value;
    uiState.filterSearch = el('#filterSearch').value;
}
function applyUIState() {
    el('#filterCategory').value = uiState.filterCategory;
    el('#filterStatus').value = uiState.filterStatus;
    el('#filterSearch').value = uiState.filterSearch;
}
function render(){
  applyUIState();
  renderPoolTasks();
  const cat = uiState.filterCategory;
  const st = uiState.filterStatus;
  const q = uiState.filterSearch.trim().toLowerCase();
  
  const cols={ today:el('#colToday'), progress:el('#colProgress'), backlog:el('#colBacklog'), done:el('#colDone') };
  Object.values(cols).forEach(c=> c.innerHTML='');

  state.tasks
    .filter(t=>!cat||t.category===cat)
    .filter(t=>!st||t.status===st)
    .filter(t=>{
      if(!q) return true;
      const inDod=(t.dod||[]).some(d=>(d.text||'').toLowerCase().includes(q));
      return (t.title||'').toLowerCase().includes(q) || (t.notes||'').toLowerCase().includes(q) || inDod;
    })
    .sort((a,b)=> (a.status!==b.status ? 0 : new Date(a.due||0) - new Date(b.due||0)))
    .forEach(t=> { const card=buildCard(t); cols[t.status]?.appendChild(card); });

  renderStats();
  renderPEMList();
}

// ===== Task Creation & IO =====
function createTaskFromForm(){
  const title=el('#title').value.trim(); if(!title) return alert('请输入标题');
  const category=el('#category').value;
  const kind = el('#kind')?.value || 'core';
  const weight = toInt(el('#weight')?.value || 5);
  const psm = (el('#psm')?.value||'').trim();
  const dodLines=el('#dod').value.split(/\n/).map(s=>s.trim()).filter(Boolean).map(s=>({text:s,done:false}));
  let due = el('#dueAt').value? new Date(el('#dueAt').value).toISOString(): null;
  const txVal=Number(el('#txValue').value||0); const txUnit=el('#txUnit').value;
  if(!due && txVal>0){ const d=new Date(); if(txUnit==='h') d.setHours(d.getHours()+txVal); else d.setDate(d.getDate()+txVal); due=d.toISOString(); }
  const repeat=el('#repeat').value || 'none';
  const t={ id:uid(), title, category, kind, weight, psm, dod:dodLines, due, status:el('#bucket').value||'today', notes:'', repeat, timeLogs:[] };
  state.tasks.unshift(t); saveLocal(); updateAndRender();
  ['#title','#dod','#psm','#txValue','#dueAt'].forEach(s=> el(s).value=''); el('#txUnit').value='h'; el('#bucket').value='today'; el('#repeat').value='none';
}
function quickAdd(){
  const title=(el('#qcTitle').value||'').trim(); if(!title) return;
  const t={ id:uid(), title, category:el('#qcCategory').value, kind:'core', weight:5, psm:'', dod:[], due:null, status:el('#qcBucket').value||'today', notes:'', repeat:'none', timeLogs:[] };
  state.tasks.unshift(t); el('#qcTitle').value=''; saveLocal(); updateAndRender();
}
function exportMarkdown(){
  const lines=['# QuestBoard 任务清单（'+new Date().toLocaleString()+'）'];
  for(const status of ['today','progress','backlog','done']){
    const subset=state.tasks.filter(t=>t.status===status); if(!subset.length) continue;
    lines.push('\n## '+statusLabel(status));
    subset.forEach((t)=>{
      lines.push('\n- **'+t.title+'** · '+t.category+' · '+kindLabel(t.kind)+' · '+(t.due?('截止：'+new Date(t.due).toLocaleString()):'未设截止'));
      if(t.psm) lines.push('  - PSM：'+t.psm);
      if(t.dod?.length){ t.dod.forEach(d=> lines.push('  - ['+(d.done?'x':' ')+'] '+d.text)); }
      if(t.timeLogs?.length){ lines.push('  - 已计时：'+ totalMinutes(t) +' 分钟'); }
      if(t.notes){ lines.push('  - 备注：'+t.notes); }
    });
  }
  download(new Blob([lines.join('\n')],{type:'text/markdown;charset=utf-8'}), 'QuestBoard-'+dateKey(now())+'.md');
}
function exportJSON(){ download(new Blob([JSON.stringify(state,null,2)],{type:'application/json'}), 'QuestBoard-'+dateKey(now())+'.json'); }
function download(blob, filename){ const url=URL.createObjectURL(blob); const a=document.createElement('a'); a.href=url; a.download=filename; a.click(); URL.revokeObjectURL(url); }
el('#importJSON').addEventListener('change', (e)=>{
  const f=e.target.files?.[0]; if(!f) return;
  const fr=new FileReader(); fr.onload=()=>{
    try{
      const obj=JSON.parse(fr.result);
      if(obj.tasks&&obj.stats){ Object.assign(state,obj); fixupState(); saveLocal(); refreshCategoriesUI(); updateAndRender(); }
      else alert('文件格式不正确');
    }catch(err){ alert('解析失败'); }
  };
  fr.readAsText(f,'utf-8'); e.target.value='';
});

// ===== Task Lifecycle =====
function snooze(t, minutes){
  const base = t.due? new Date(t.due): new Date();
  base.setMinutes(base.getMinutes()+minutes);
  t.due = base.toISOString();
  saveLocal(); updateAndRender();
}
function completeTask(t){
  t.status='done'; if(!t.completedAt) t.completedAt=new Date().toISOString();
  addMVD();
  const rep = t.repeat || 'none';
  if(rep && rep!=='none'){
    const next = JSON.parse(JSON.stringify(t));
    next.id = uid();
    next.status = 'today';
    next.completedAt = null;
    next.dod = (next.dod||[]).map(d=>({text:d.text, done:false}));
    const base = t.due? new Date(t.due): new Date();
    if(rep==='daily') base.setDate(base.getDate()+1);
    if(rep==='weekly') base.setDate(base.getDate()+7);
    if(rep==='monthly') base.setMonth(base.getMonth()+1);
    next.due = base.toISOString();
    state.tasks.unshift(next);
  }
  saveLocal(); updateAndRender();
}

// ===== Pomodoro (no changes) =====
function startPomodoro(seconds, taskId=null){
  stopTimer();
  state.remaining=seconds;
  state.timerTaskId = taskId;
  state.timerOrigSeconds = seconds;
  renderTimer(); el('#btnStopTimer').classList.remove('hidden');
  state.timer=setInterval(()=>{
    state.remaining-=1;
    if(state.remaining<=0){
      stopTimer();
      addPomodoro();
      if(state.timerTaskId){
        const t = state.tasks.find(x=>x.id===state.timerTaskId);
        if(t){ logMinutes(t, Math.round((state.timerOrigSeconds||0)/60)); }
      }
      alert('计时完成！休息一下~');
    }
    renderTimer();
  },1000);
}
function stopTimer(){ if(state.timer){ clearInterval(state.timer); state.timer=null; } state.timerTaskId=null; state.timerOrigSeconds=null; el('#btnStopTimer').classList.add('hidden'); renderTimer(); }
function renderTimer(){ el('#pomodoroClock').textContent = state.timer? fmtTime(state.remaining): '—'; }
function fmtTime(s){ const m=Math.floor(s/60).toString().padStart(2,'0'); const ss=Math.floor(s%60).toString().padStart(2,'0'); return `${m}:${ss}`; }

// ===== Weekly Review (no changes) =====
function enumerateLast7(){
  const end = new Date(); end.setHours(23,59,59,999);
  const start = new Date(end); start.setDate(start.getDate()-6); start.setHours(0,0,0,0);
  const arr=[]; const d=new Date(start);
  while(d<=end){ arr.push(dateKey(d)); d.setDate(d.getDate()+1); }
  return arr;
}
function weeklyMarkdown(rangeDays, doneList, mvdVals, pomoVals, minsVals){
  const start = rangeDays[0], end = rangeDays[rangeDays.length-1];
  const doneStr = doneList.slice(0,20).map(t=>`- ${t.title} · ${t.category||'未分类'}`).join('\n') || '- （本期无完成）';
  return `# 周回顾（${start} — ${end}）
- 总用时：${minsVals.reduce((a,b)=>a+b,0)} 分钟（每日：${minsVals.join('/')}）
- MVD：${mvdVals.reduce((a,b)=>a+b,0)}（每日：${mvdVals.join('/')}）
- 番茄：${pomoVals.reduce((a,b)=>a+b,0)}（每日：${pomoVals.join('/')}}）

## 已完成（最近20条）
${doneStr}`;
}
function openWeekly(){
  const body=el('#weeklyBody'); const days=enumerateLast7();
  const mvd=days.map(k=> state.stats.mvdByDate[k]||0);
  const pomo=days.map(k=> state.stats.pomodoroByDate[k]||0);
  const mins=days.map(k=> state.stats.minutesByDate[k]||0);
  const doneAll=state.tasks.filter(t=>t.status==='done');
  const valid = doneAll.filter(t=> t.due && t.completedAt);
  let onTime = 0; for(const t of valid){ if(new Date(t.completedAt) <= new Date(t.due)) onTime++; }
  const onTimeRate = valid.length ? Math.round(onTime*100/valid.length) : 0;
  const daySet = new Set(days);
  const catMap = {}; const kindMap={};
  (state.tasks||[]).forEach(t=>{
    (t.timeLogs||[]).forEach(log=>{
      if(daySet.has(log.date)){
        const m = toInt(log.minutes);
        const cat = t.category||'未分类'; catMap[cat]=(catMap[cat]||0)+m;
        const kd = t.kind||'core'; kindMap[kd]=(kindMap[kd]||0)+m;
      }
    });
  });
  const bars = (vals)=>{
    const max = Math.max(1, ...vals);
    return vals.map(v=> `<div class="bar" style="height:${Math.round(v*100/max)}%"></div>`).join('');
  };
  const catRows = Object.entries(catMap).sort((a,b)=>b[1]-a[1]).slice(0,6);
  const catMax = Math.max(1, ...catRows.map(x=>x[1]));
  const catHTML = catRows.map(([name,val])=>`
    <div class="catbar">
      <div class="name">${name}</div>
      <div class="track"><div class="fill" style="width:${Math.round(val*100/catMax)}%"></div></div>
      <div class="val">${val}′</div>
    </div>`).join('') || `<div class="text-xs text-slate-500">本期无用时数据</div>`;
  const totalMin = mins.reduce((a,b)=>a+b,0) || 1;
  const kindLine = ['core','enabling','admin','life'].map(k=>{
    const v = toInt(kindMap[k]||0);
    const pct = Math.round(v*100/totalMin);
    const name = {core:'主业',enabling:'赋能',admin:'行政',life:'生活'}[k];
    return `<span>${name} ${v}′（${pct}%）</span>`;
  }).join(' · ');
  body.innerHTML = `
    <div class="text-xs text-slate-500">范围：${days[0]} — ${days[6]}</div>
    <div class="kpis">
      <div class="kpi"><div class="label">总用时（分钟）</div><div class="value">${totalMin}</div><div class="bars">${bars(mins)}</div><div class="text-xs text-slate-500">近7天</div></div>
      <div class="kpi"><div class="label">MVD 合计</div><div class="value">${mvd.reduce((a,b)=>a+b,0)}</div><div class="bars">${bars(mvd)}</div><div class="text-xs text-slate-500">近7天</div></div>
      <div class="kpi"><div class="label">番茄合计</div><div class="value">${pomo.reduce((a,b)=>a+b,0)}</div><div class="bars">${bars(pomo)}</div><div class="text-xs text-slate-500">近7天</div></div>
    </div>
    <div class="card bg-white p-3 mt-2"><h4 class="font-semibold mb-2">⏱ 用时结构（类别 Top6）</h4><div class="catbars">${catHTML}</div><div class="text-xs text-slate-500 mt-2">类型占比：${kindLine}</div></div>
    <div class="card bg-white p-3 mt-2"><h4 class="font-semibold mb-2">✅ 完成情况（按时 vs 逾期）</h4><div class="donut-wrap"><div class="donut" style="--p:${onTimeRate};"></div><div class="legend"><span><i class="dot em"></i>按时：${onTime}</span><span><i class="dot rose"></i>逾期：${valid.length - onTime}</span><span>（总 ${valid.length}）</span></div></div></div>
    <div class="card bg-white p-3 mt-2"><h4 class="font-semibold mb-2">📝 Markdown 摘要</h4><pre class="bg-slate-50 p-3 rounded text-xs whitespace-pre-wrap">${weeklyMarkdown(days, doneAll, mvd, pomo, mins)}</pre></div>
  `;
  el('#modalWeekly')?.classList.remove('hidden');
  el('#btnCopyWeekly')?.addEventListener('click', ()=>{ navigator.clipboard?.writeText(weeklyMarkdown(days, doneAll, mvd, pomo, mins)); alert('已复制周回顾 Markdown'); }, {once:true});
}

// ===== Help & Nav (no changes) =====
function openHelp(){ el('#modalHelp').classList.remove('hidden'); }
function goto(sel){ const t=el(sel); if(t) t.scrollIntoView({behavior:'smooth', block:'start'}); }
document.addEventListener('click', (e)=>{ if(e.target.matches('[data-goto]')) goto(e.target.dataset.goto); });
document.addEventListener('keydown', (e)=>{
  if(e.key==='/' && !['INPUT','TEXTAREA','SELECT'].includes(document.activeElement.tagName)){ e.preventDefault(); el('#filterSearch').focus(); }
  if(e.key==='n' || e.key==='N'){ goto('#new'); }
  if(e.key==='t' || e.key==='T'){ goto('#today'); }
  if(e.key==='w' || e.key==='W'){ openWeekly(); }
});

// ===== PEM & Pool (no changes) =====
el('#btnQuickAdd').addEventListener('click', quickAdd);
function refreshPoolFilter(){
  const sel = document.querySelector('#poolFilterCategory');
  if(!sel) return;
  sel.innerHTML='';
  const opAll = document.createElement('option'); opAll.value=''; opAll.textContent='全部'; sel.appendChild(opAll);
  state.categories.forEach(c=>{ const op=document.createElement('option'); op.value=c; op.textContent=c; sel.appendChild(op); });
}
function renderPoolTasks(){
  const box = document.querySelector('#poolTasks'); if(!box) return;
  const cat = (document.querySelector('#poolFilterCategory')?.value)||'';
  const list = state.tasks.filter(t=> (t.status==='today' || t.status==='backlog') && (!cat || t.category===cat));
  if(!list.length){ box.innerHTML = '<div class="text-xs text-slate-500">当前无任务（或被过滤）。</div>'; return; }
  box.innerHTML = '';
  list
    .sort((a,b)=> new Date(a.due||0) - new Date(b.due||0))
    .forEach(t=>{
      const row = document.createElement('div');
      row.className='p-2 rounded-lg border border-slate-200 flex items-center justify-between gap-2';
      const left = document.createElement('div');
      const dueStr = t.due? new Date(t.due).toLocaleString(): '未设截止';
      left.innerHTML = '<div class="text-sm font-medium">'+escapeHtml(t.title)+'</div>' +
                       '<div class="text-xs text-slate-500">'+escapeHtml(t.category)+' · '+dueStr+' · '+(t.status==='today'?'今日':'待办')+'</div>';
      const btns = document.createElement('div'); btns.className='flex gap-2';
      const bMove = document.createElement('button'); bMove.className='px-2 py-1 rounded bg-emerald-100 text-emerald-800 text-xs'; bMove.textContent='▶ 推进进行中'; bMove.onclick=()=>{ t.status='progress'; saveLocal(); updateAndRender(); renderPoolTasks(); };
      const bStart = document.createElement('button'); bStart.className='px-2 py-1 rounded bg-amber-100 text-amber-800 text-xs'; bStart.textContent='开始25′'; bStart.onclick=()=> startPomodoro(25*60, t.id);
      const bAttach = document.createElement('button'); bAttach.className='px-2 py-1 rounded bg-sky-100 text-sky-800 text-xs'; bAttach.textContent='附包裹'; bAttach.onclick=()=> openAttachPEM(t.id);
      const bSnooze = document.createElement('button'); bSnooze.className='px-2 py-1 rounded bg-slate-100 text-xs'; bSnooze.textContent='⏰+1h'; bSnooze.onclick=()=>{ snooze(t,60); renderPoolTasks(); };
      btns.append(bMove,bStart,bAttach,bSnooze);
      row.append(left, btns); box.appendChild(row);
    });
}
function renderPEMList(){
  const box=el('#pemList'); if(!box) return; box.innerHTML='';
  if(!state.packages.length){ box.innerHTML='<div class="text-xs text-slate-500">还没有包裹。可从下方“新建包裹”添加。</div>'; return; }
  state.packages.forEach(p=>{
    const div=document.createElement('div'); div.className='p-2 rounded-lg border border-slate-200 flex items-center justify-between gap-2';
    const meta=document.createElement('div'); meta.innerHTML='<div class="text-sm font-medium">'+escapeHtml(p.title)+'</div><div class="text-xs text-slate-500">预热 '+(p.minutes||15)+'′ · 输入 '+(p.inputs?.length||0)+' · 步骤 '+(p.steps?.length||0)+'</div>';
    const btns=document.createElement('div'); btns.className='flex gap-2';
    const bAttach=document.createElement('button'); bAttach.className='px-2 py-1 rounded bg-sky-100 text-sky-800 text-xs'; bAttach.textContent='附到任务'; bAttach.onclick=()=> openAttachPEM();
    const bEdit=document.createElement('button'); bEdit.className='px-2 py-1 rounded bg-slate-100 text-xs'; bEdit.textContent='编辑'; bEdit.onclick=()=> editPEM(p.id);
    const bDel=document.createElement('button'); bDel.className='px-2 py-1 rounded bg-rose-100 text-rose-800 text-xs'; bDel.textContent='删'; bDel.onclick=()=> deletePEM(p.id);
    btns.append(bAttach,bEdit,bDel); div.append(meta,btns); box.appendChild(div);
  });
  const sel=el('#attachPEMSelect'); if(sel){ sel.innerHTML=''; state.packages.forEach(p=>{ const op=document.createElement('option'); op.value=p.id; op.textContent=p.title; sel.appendChild(op); }); }
}
function addPEM(){
  const title=(el('#pemTitle').value||'').trim(); if(!title) return alert('请输入包裹名称');
  const minutes=Number(el('#pemMinutes').value||15);
  const links=(el('#pemLinks').value||'').split(',').map(s=>s.trim()).filter(Boolean);
  const inputs=(el('#pemInputs').value||'').split(/\n/).map(s=>s.trim()).filter(Boolean);
  const steps=(el('#pemSteps').value||'').split(/\n/).map(s=>s.trim()).filter(Boolean);
  state.packages.push({ id:uid(), title, minutes, links, inputs, steps });
  saveLocal(); renderPEMList();
  ['#pemTitle','#pemMinutes','#pemLinks','#pemInputs','#pemSteps'].forEach(s=>{ const n=el(s); if(n) n.value=(s==='#pemMinutes'?'15':''); });
}
function findPEM(id){ return state.packages.find(p=>p.id===id); }
function editPEM(id){
  const p=findPEM(id); if(!p) return;
  const title=prompt('包裹名称：', p.title)||p.title;
  const mins=Number(prompt('预热分钟：', p.minutes)||p.minutes);
  p.title=title; p.minutes=mins; saveLocal(); renderPEMList();
}
function deletePEM(id){
  if(!confirm('删除该包裹？')) return;
  state.packages=state.packages.filter(p=>p.id!==id); saveLocal(); renderPEMList();
}
let pendingAttachTaskId = null;
function openAttachPEM(taskId){
  pendingAttachTaskId = taskId || null;
  renderPEMList();
  el('#modalAttachPEM').classList.remove('hidden');
}
el('#btnApplyPEM').addEventListener('click', ()=>{
  const sel=el('#attachPEMSelect'); const pid=sel.value; const p=findPEM(pid); if(!p) return alert('请选择包裹');
  if(!pendingAttachTaskId){ alert('请到具体任务卡片点击“附包裹”。'); return; }
  const t=state.tasks.find(x=>x.id===pendingAttachTaskId); if(!t) return;
  const block = ['【PEM 包裹】'+p.title, p.links?.length? ('链接：'+p.links.join(' , ')) : '', p.inputs?.length? ('所需输入：\n- '+p.inputs.join('\n- ')) : '', p.steps?.length? ('预热步骤：\n- '+p.steps.join('\n- ')) : ''].filter(Boolean).join('\n') + '\n';
  t.notes = (t.notes||'') + (t.notes? '\n':'') + block;
  saveLocal(); updateAndRender();
  startPomodoro((p.minutes||15)*60, t.id);
  el('#modalAttachPEM').classList.add('hidden'); pendingAttachTaskId=null;
});

// ===== Drag & Drop (no changes) =====
els('.droptarget').forEach(box=>{
  box.addEventListener('dragover', (e)=>{ e.preventDefault(); box.classList.add('dragover'); });
  box.addEventListener('dragleave', ()=> box.classList.remove('dragover'));
  box.addEventListener('drop', (e)=>{
    e.preventDefault(); box.classList.remove('dragover');
    const id = e.dataTransfer.getData('text/plain'); const t=state.tasks.find(x=>x.id===id); if(!t) return;
    const sec = box.id || box.closest('section')?.id;
    if(sec==='colToday' || sec==='today') t.status='today';
    else if(sec==='colProgress' || sec==='progress') t.status='progress';
    else if(sec==='colBacklog' || sec==='backlog') t.status='backlog';
    else if(sec==='colDone' || sec==='done') { t.status='done'; if(!t.completedAt) t.completedAt=new Date().toISOString(); }
    saveLocal(); updateAndRender();
  });
});

// ===== 同步 (v0.7.5 logic, unchanged) =====
const { get, set, del } = idbKeyval;
const SYNC_HANDLE_KEY = 'syncFileHandle';
let syncHandle=null;
let autoSave=true;
let saveTimer=null;

async function verifyPermission(fileHandle, readWrite) {
  const options = {};
  if (readWrite) { options.mode = 'readwrite'; }
  if ((await fileHandle.queryPermission(options)) === 'granted') return true;
  if ((await fileHandle.requestPermission(options)) === 'granted') return true;
  return false;
}

async function loadStateFromFileHandle(handle, silent = false) {
    try {
        const file = await handle.getFile();
        const text = await file.text();
        const obj = JSON.parse(text);
        if (obj.tasks && obj.stats) {
            Object.assign(state, obj);
            fixupState(); // Now this won't nuke your categories
            localStorage.setItem(LS_KEY, JSON.stringify(state));
            return true;
        } else if (!silent) {
            alert('文件格式不正确。');
        }
    } catch (err) {
        console.error('读取或解析文件失败:', err);
        if (!silent) alert('读取或解析文件失败: ' + err.message);
    }
    return false;
}

async function initSync() {
  try {
    const handle = await get(SYNC_HANDLE_KEY);
    if (handle) {
      if (await verifyPermission(handle, true)) {
        syncHandle = handle;
        el('#syncInfo').textContent = '已自动连接：' + (handle.name || 'QuestBoard.json');
        return await loadStateFromFileHandle(handle, true);
      } else {
        await del(SYNC_HANDLE_KEY);
        el('#syncInfo').textContent = '连接已失效，请重新选择文件。';
        return false;
      }
    }
  } catch (err) {
    console.error("初始化同步失败:", err);
    el('#syncInfo').textContent = '浏览器不支持或禁止了文件访问。';
  }
  return false;
}

async function pickSyncFile(){
    try {
        const [handle] = await window.showOpenFilePicker({
            types: [{ description: 'JSON Files', accept: { 'application/json': ['.json'] } }],
        });

        if (handle) {
            const loaded = await loadStateFromFileHandle(handle, false);

            if (loaded) {
                syncHandle = handle;
                await set(SYNC_HANDLE_KEY, handle);
                el('#syncInfo').textContent = '已连接：' + handle.name;
                updateAndRender(); // Use updateAndRender here too
                alert('文件已成功链接！');
            }
        }
    } catch (err) {
        if (err.name !== 'AbortError') {
            console.error('选择文件时出错:', err);
            alert('选择文件时出错，请检查浏览器控制台。');
        }
    }
}

async function saveToSyncFile(){
  if(!syncHandle) return;
  try {
    if (!(await verifyPermission(syncHandle, true))) {
        el('#syncInfo').textContent = '保存失败：文件写入权限已失效。';
        return;
    }
    const writable = await syncHandle.createWritable();
    await writable.write(JSON.stringify(state, null, 2));
    await writable.close();
    el('#syncInfo').textContent = '已连接：' + (syncHandle.name || 'QuestBoard.json') + ' · 已保存 ' + new Date().toLocaleTimeString();
  } catch(err) {
    console.error("保存文件失败:", err);
    el('#syncInfo').textContent = '保存失败，请检查文件权限。';
  }
}

function maybeAutoSaveFile(){
    if(!autoSave || !syncHandle) return;
    if(saveTimer) clearTimeout(saveTimer);
    saveTimer=setTimeout(()=> saveToSyncFile(), 500);
}

// ===== 提醒 (no changes) =====
async function ensureNotifyPermission(){ /* ... */ }
function startReminderLoop(){ setInterval(()=>{ if(!state.meta.notifyEnabled) return; checkReminders(); }, 30000); }
function checkReminders(){ /* ... */ }

// ===== 事件绑定 (no changes) =====
el('#btnAdd').addEventListener('click', createTaskFromForm);
el('#btnExportMD').addEventListener('click', exportMarkdown);
el('#btnExportJSON').addEventListener('click', exportJSON);
el('#btnPomodoro25').addEventListener('click', ()=>startPomodoro(25*60));
el('#btnStopTimer').addEventListener('click', stopTimer);
el('#btnResetState').addEventListener('click', ()=>{ if(confirm('将清空所有本地数据，确定吗？')){ localStorage.removeItem(LS_KEY); window.location.reload(); }});
el('#btnClearFilters').addEventListener('click', ()=>{ 
    el('#filterCategory').value=''; 
    el('#filterStatus').value=''; 
    el('#filterSearch').value=''; 
    updateAndRender(); 
});
el('#filterCategory').addEventListener('change', updateAndRender);
el('#filterStatus').addEventListener('change', updateAndRender);
el('#filterSearch').addEventListener('input', ()=> updateAndRender());
el('#btnWeekly')?.addEventListener('click', openWeekly);
el('#btnHelp')?.addEventListener('click', openHelp);
el('#btnAddCategory').addEventListener('click', addCategory);
el('#btnPickSync').addEventListener('click', async () => {
    captureUIState();
    await pickSyncFile();
});
el('#btnDisconnectSync')?.addEventListener('click', async () => {
    await del(SYNC_HANDLE_KEY);
    syncHandle = null;
    el('#syncInfo').textContent = '未连接同步文件';
    alert('已断开连接。');
});
el('#chkAutoSave').addEventListener('change', (e)=>{ autoSave=e.target.checked; if(autoSave) maybeAutoSaveFile(); });
el('#chkNotify').addEventListener('change', async (e)=>{ state.meta.notifyEnabled=e.target.checked; saveLocal(); if(state.meta.notifyEnabled){ const ok=await ensureNotifyPermission(); if(!ok){ alert('浏览器未授予通知权限'); el('#chkNotify').checked=false; state.meta.notifyEnabled=false; saveLocal(); } }});
el('#btnAddPEM').addEventListener('click', addPEM);

document.addEventListener('click', (e)=>{
  if(e.target.matches('[data-close]')) e.target.closest('.fixed').classList.add('hidden');
  if(e.target.classList.contains('absolute') && e.target.classList.contains('inset-0') && e.target.classList.contains('bg-black/40')){
    e.target.parentElement.classList.add('hidden');
  }
});
document.addEventListener('change', (e)=>{ if(e.target && e.target.id==='poolFilterCategory'){ renderPoolTasks(); }});
document.addEventListener('click', (e)=>{ if(e.target && e.target.id==='btnRefreshPool'){ renderPoolTasks(); }});

// ===== Init (no changes) =====
async function initializeApp() {
    captureUIState();
    const syncLoaded = await initSync();
    if (!syncLoaded) {
        loadLocal();
        fixupState();
    }
    refreshCategoriesUI();
    render();
    el('#chkNotify').checked = !!state.meta.notifyEnabled;
    el('#chkAutoSave').checked = autoSave;
    startReminderLoop();
}

initializeApp();